/* Copyright (c) 2016-2020 Enfry Ltd. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of Enfry. 
 * You shall not disclose such Confidential Information
 * and shall use it only in accordance with the terms of the agreements
 * you entered into with Enfry.
 */
package com.enfry.common.util;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.zip.GZIPInputStream;

import com.enfry.common.util.asserts.Asserts;
import com.enfry.common.util.constants.CodeConstants;
import com.enfry.common.util.system.Systems;

/**
 * 
 * 流工具类，注意，本类中的方法都没有对输入输出流作flush和close操作，需要业务层实现
 * @Title com.enfry.util.Streams
 * @author Penghu
 * @date 2016年11月10日
 */
public class Streams implements CodeConstants
{
    /** 读文件中字节数 */
    public static byte[] getBytesFilePath(File file) throws IOException
    {
        if (file == null || !file.isFile() || !file.canRead())
            return null;

        FileInputStream input = new FileInputStream(file);
        try
        {
            int len = input.available();
            int readcount = 0, ret = 0;
            byte[] buf = new byte[len];
            while (readcount < len)
            {
                ret = input.read(buf, readcount, len - readcount);
                if (ret == -1)
                    throw new EOFException("按长度读消息时,长度不够即到达流尾端");
                readcount += ret;
            }
            return buf;
        }
        finally
        {
            input.close();
        }
    }

    /** 读流成字符串,指定编码 */
    public static String getString(InputStream stream, String charset) throws IOException
    {
        try
        {
            BufferedReader reader = new BufferedReader(new InputStreamReader(stream, charset));
            StringWriter writer = new StringWriter();

            char[] chars = new char[256];
            int count = 0;
            while ((count = reader.read(chars)) > 0)
            {
                writer.write(chars, 0, count);
            }

            return writer.toString();
        }
        finally
        {
            if (stream != null)
            {
                stream.close();
            }
        }
    }
    
    /**对Gzip的流进行解析读取字符串 */
    public static String getStringGzip(InputStream stream, String charset) throws IOException
    {
        if (stream == null)
            return null;
        
        GZIPInputStream gzip = new GZIPInputStream(stream);
        
        try
        {
            BufferedReader reader = new BufferedReader(new InputStreamReader(gzip, charset));
            StringWriter writer = new StringWriter();

            char[] chars = new char[256];
            int count = 0;
            while ((count = reader.read(chars)) > 0)
            {
                writer.write(chars, 0, count);
            }

            return writer.toString();
        }
        finally
        {
            stream.close();
            gzip.close();
        }
    }
    
    /**
     * 字符串转流，GBK编码，内容为null时返回null
     * 
     * @param content   内容
     * @return          ByteArrayInputStream
     */
    public static ByteArrayInputStream toStreamGBK(String content)
    {
        return toStream(content, _GBK_);
    }
    
    /**
     * 字符串转流，UTF-8编码，内容为null时返回null
     * 
     * @param content   内容
     * @return          ByteArrayInputStream
     */
    public static ByteArrayInputStream toStreamUTF8(String content)
    {
        return toStream(content, _UTF_8_);
    }
    
    /**
     * 字符串转流，内容为null时返回null
     * 
     * @param content   内容
     * @param charset   字符集
     * @return          ByteArrayInputStream
     */
    public static ByteArrayInputStream toStream(String content, String charset)
    {
        if (content == null)
            return null;
        
        try
        {
            return new ByteArrayInputStream(content.getBytes(charset));
        }
        catch (UnsupportedEncodingException e)
        {
            throw Asserts.exception("非法的字符编码["+charset+"]", e);
        }
    }

    /** 读流中字节数 */
    public static byte[] getBytes(InputStream input, int len) throws IOException
    {
        int readcount = 0, ret = 0;
        byte[] buffer = new byte[len];
        while (readcount < len)
        {
            ret = input.read(buffer, readcount, len - readcount);
            if (ret == -1)
                throw new EOFException("按长度读消息时,长度不够即到达流尾端");
            readcount += ret;
        }
        return buffer;
    }
    
    /** 读流中资源 */
    public static long putBytes(InputStream input, OutputStream output) throws IOException
    {
        byte[] buffer = new byte[1024];
        long count = 0;
        int n = 0;
        while (-1 != (n = input.read(buffer)))
        {
            output.write(buffer, 0, n);
            count += n;
        }
        return count;
    }

    /** 读CLASSPATH中资源 */
    public static byte[] getBytesClassPath(Class<?> clazz, String path) throws IOException
    {
        InputStream in = clazz.getResourceAsStream(path);
        int len = in.available();
        byte[] buf = new byte[len];
        int off = 0;
        while (off < len)
        {
            int readLen = (len - off > 32) ? 32 : len - off;
            int ret = in.read(buf, off, readLen);
            if (ret == -1)
                throw new EOFException("读[" + path + "]时按长度读消息时,长度不够即到达流尾端");
            off += ret;
        }
        return buf;
    }

    /** 读CLASSPATH中资源 */
    public static void putBytesClassPath(Class<?> clazz, String path, OutputStream output) throws IOException
    {
        InputStream in = clazz.getResourceAsStream(path);
        int len = in.available();
        byte[] buf = new byte[1024];
        int off = 0;
        while (off < len)
        {
            int readLen = (len - off > 1024) ? 1024 : len - off;
            int ret = in.read(buf, 0, readLen);
            if (ret == -1)
                throw new EOFException("读[" + path + "]时按长度读消息时,长度不够即到达流尾端");
            off += ret;

            output.write(buf, 0, ret);
        }
    }

    /** 
     * 读流获取一行，指定最大长度，偏移量指定为0，按顺序读取
     * 
     * @param input 流
     * @param b 放进的缓存
     * @param len 支持的最大长度
     */
    public static int readLine(InputStream input, byte[] b, int len) throws IOException
    {
        return readLine(input, b, 0, len);
    }
    
    /** 
     * 读流获取一行，指定偏移量和最大长度
     * 
     * @param input 流
     * @param b 放进的缓存
     * @param off 流字节偏移量
     * @param len 支持的最大长度
     */
    public static int readLine(InputStream input, byte[] b, int off, int len) throws IOException
    {
        if (len <= 0)
            return 0;

        int count = 0, c;
        while ((c = input.read()) != -1)
        {
            b[off++] = (byte) c;
            count++;
            if (c == '\n' || count == len)
                break;
        }

        return count > 0 ? count : -1;
    }
    
    /**
     * 依次检测流的编码格式，如果有BOM则以BOM为准，否则判断是否是UTF-8，如果不是则是ANSI
     * 
     * @param bytes     字节数组
     * @param len       指定的长度
     * @return          如果有BOM则取BOM，否则判断是否是UTF-8，如果不是则是ANSI
     */
    public static String getStreamEncoding(byte[] bytes, int len)
    {
        if (bytes == null || bytes.length < 2 || len < 2 || bytes.length - len < 2)
            return null;//null,0,1用UTF-8没问题
        
        //有BOM，以BOM为准
        String encoding = getStreamEncodingBOM(bytes);
        if (encoding != null)
            return encoding;
        
        //没BOM，逐个字节检查是否是UTF-8
        if (bytes.length < len)
            len = bytes.length;
        
        //UTF-8 有以下编码规则：11100101 10001000 10011010表示"刚"字,01101001表示"i"
        //如果高1位=0，表示这是一个 ASCII 字符（00 - 7F）。这里GBK,ISO8859-1,UTF-8相同。                        01101001 表示 "i"
        //如果高2位=10，表示它不是首字节，需要向前查找才能得到当前字符的首字节，一起组成一个字符。
        //如果高2位=11，表示它是字符首字节
        //             1)高3位=110表示该字符由2个字节组成，后一个字节高2位=10，如俄文字符等。            11010000 10111011 表示 "л"
        //             2)高3位=111表示该字符由3个字节组成，后两个字节高2位=10，如中文字符等。            11100101 10001000 10011010 表示 "刚"
        //
        encoding = Systems.getSystemEncoding();
        byte firstChar=0;int hasNum=0;int num=0;
        for (int i=0;i<len;i++)
        {
            byte b = bytes[i];
            if (Bits.isZero(b, 1))
            {//高1位=0的ANSI
                
                //测试时否满足UTF-8规范，不满足返回本地编码
                if (firstChar != 0 && hasNum !=0 && hasNum != num)
                    return encoding;
                
                firstChar = 0;
                hasNum = 0;
                num = 0;
                continue;
            }
            
            //高1位=1时，判断高第2位
            if (Bits.isZero(b, 2))
            {
                //高第2位是0,表示前面有首字节，如果没有则测试未通过，返回本地编码
                if (firstChar == 0)
                    return encoding;
                
                //通过个数++
                num++;
            }
            else
            {//高第2位是1，表示首字节
                
                //测试时否满足UTF-8规范，不满足返回本地编码
                if (firstChar != 0 && hasNum !=0 && hasNum != num)
                    return encoding;
                
                //否则测试通过，重新下一个
                firstChar = b;
                hasNum = (Bits.isZero(b, 3))?1:2;
                num = 0;
            }
        }
        
        //最后一个，测试时否满足UTF-8规范，不满足返回本地编码
        if (firstChar != 0 && hasNum !=0 && hasNum != num)
            return encoding;
        
        //全部测试通过，返回UTF-8
        return _UTF_8_;
    }
    
    /**
     * 根据指定的流检查是否有BOM头的编码格式
     * 
     * @param b 字节数组
     * @return UTF-8/UTF-16BE/UTF-16LE/UTF-32BE/UTF-32LE/如果都不是则为null
     */
    public static String getStreamEncodingBOM(byte[] b)
    {
        if (b == null || b.length < 2)
            return null;
        
        if (b.length >= 4 && isUTF32BEBOM(b[0], b[1], b[2], b[3]))
            return _UTF_32BE_;
        else if (b.length >= 4 && isUTF32LEBOM(b[0], b[1], b[2], b[3]))
            return _UTF_32LE_;
        else if (b.length >= 3 && isUTF8BOM(b[0], b[1], b[2]))
            return _UTF_8_;
        else if (isUTF16BEBOM(b[0], b[1]))
            return _UTF_16BE_;
        else if (isUTF16LEBOM(b[0], b[1]))
            return _UTF_16LE_;
        
        return null;
    }
    
    /** 是否是UTF8格式//EF BB BF */
    public static boolean isUTF8BOM(byte b0, byte b1, byte b2)
    {
        return (b0 &0xFF) == 0xEF && (b1 & 0xFF) == 0xBB  && (b2 & 0xFF) == 0xBF;
    }
    
    /** 是否是UTF16LE格式//FF FE */
    public static boolean isUTF16LEBOM(byte b0, byte b1)
    {
        return (b0 &0xFF) == 0xFF && (b1 & 0xFF) == 0xFE;
    }
    
    /** 是否是UTF16BE格式//FE FF */
    public static boolean isUTF16BEBOM(byte b0, byte b1)
    {
        return (b0 &0xFF) == 0xFE && (b1 & 0xFF) == 0xFF;
    }
    
    /** 是否是UTF32BE格式//FF FE 00 00 */
    public static boolean isUTF32LEBOM(byte b0, byte b1, byte b2, byte b3)
    {
        return (b0 &0xFF) == 0xFF && (b1 & 0xFF) == 0xFE 
            && (b2 &0xFF) == 0x00 && (b3 & 0xFF) == 0x00;
    }
    
    /** 是否是UTF16BE格式 00 00 FE FF */
    public static boolean isUTF32BEBOM(byte b0, byte b1, byte b2, byte b3)
    {
        return (b0 &0xFF) == 0x00 && (b1 & 0xFF) == 0x00
            && (b2 &0xFF) == 0xFE && (b3 & 0xFF) == 0xFF;
    }
}
